/**
 * Copyright © 2012, 2013 dr. ir. Jeroen M. Valk
 * 
 * This file is part of ComPosiX.
 * 
 * ComPosiX is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * ComPosiX is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * ComPosiX. If not, see <http://www.gnu.org/licenses/>.
 */

var Superior = (function() {

	function inverse(redir) {
		var result = new Array();
		for ( var i = 0; i < redir.length; ++i) {
			if (redir[i] != null) {
				var redirect = redir[i];
				for ( var j = 0; j < redirect.length; ++j) {
					var k = redirect[j];
					if (k > 0) {
						if (typeof result[k] == 'undefined') {
							result[k] = [ i, j ];
						} else {
							throw new Error('ambiguous channel redirection');
						}
					}
				}
			}
		}
		return result;
	}

	function result(me, you, redirect, logger, name, hierarchy) {
		var counter = 0;
		var self = this;

		this.name = name;

		self.unstable = false;

		self.stdout = new Fifo();
		self.stdout.open(-1);
		self.stdin = new Fifo();
		self.stdin.open(-1);

		continuation();

		var subordinate = new Array();
		var redir = new Array();
		self.subordinate = subordinate;
		for ( var i = 0; i < hierarchy.length; ++i) {
			redir[i] = hierarchy[i];
			var constructor = redir[i].shift();
			var aux = new constructor(i, self.stdin, redir[i]);
			subordinate[i] = aux.self;

			if (typeof subordinate[i] == 'undefined') {
				logger.error('IGNORED undefined[' + i
						+ ']: initiator has no self');
				logger.error(aux);
				subordinate[i] = null;
				redir[i] = null;
			} else {
				logger.info('INSTALLED ' + subordinate[i].name + '[' + i + ']');
			}
		}
		redir[i] = new Array();
		for ( var j = 0; j < redirect.length; ++j) {
			redir[i][j] = -redirect[j];
		}
		var lookup = inverse(redir);

		self.initialize = function() {
			for ( var i = 0; i < this.subordinate.length; ++i) {
				this.subordinate[i].initialize();
			}
		};

		function assert(condition) {
			if (!condition) {
				throw new Error('internal error');
			}
		}

		function continuation() {
			var check = false;
			counter = 0;
			self.stdin.wait(function(size) {
				assert(check);
				check = false;
				forward(size, false);
			});
			check = true;
		}

		function reading(i, msgv) {
			var result = new Array();
			for ( var j = 0; j < msgv.length; ++j) {
				var msg = msgv[j];
				if (msg.length == 2 && typeof msg[0] == 'number') {
					var fd = redir[i][msg[0]];
					if (typeof fd == 'number') {
						if (fd < 0) {
							logger.info('ON ' + -fd + ': RECV ' + msg[0]
									+ ' @ ' + subordinate[i].name + '[' + i
									+ ']:');
							logger.info(msg[1]);
							msg[0] = fd;
							result.push(msg);
						} else {
							logger.error(subordinate[i].name + '[' + i + ']: '
									+ msg[0] + ' not open for writing');
						}
					} else {
						logger.error(subordinate[i].name + '[' + i
								+ ']: bad descriptor: ' + msg[0]);
					}
				} else {
					if (typeof msg == 'number') {
						var fd = redir[i][msg];
						if (typeof fd == 'number') {
							if (fd < 0) {
								logger.info('ON ' + -fd + ': RECV ' + msg
										+ ' @ ' + subordinate[i].name + '[' + i
										+ '] = EOF');
								result.push(fd);
							} else {
								logger
										.error(subordinate[i].name + '[' + i
												+ ']: ' + msg
												+ ' not open for writing');
							}
						} else {
							logger.error(subordinate[i].name + '[' + i
									+ ']: bad descriptor: ' + msg);
						}
						assert(fd < 0);
					} else {
						logger.error(subordinate[i].name + '[' + i
								+ ']: bad message:');
						logger.error(msg);
					}
				}
			}
			return result;
		}

		function backward(i, size, back) {
			logger.trace('backward(i,size,back):');
			logger.trace(i);
			logger.trace(size);
			logger.trace(back);
			if (i < subordinate.length) {
				if (subordinate[i].unstable) {
					logger.info('CALL ' + subordinate[i].name + '[' + i + ']');
					try {
						subordinate[i].stdin.flush();
						subordinate[i].unstable = false;
					} catch (e) {
						logger.error(e.stack);
					}
					var n = subordinate[i].stdout.size();
					if (n > 0) {
						var check = false;
						subordinate[i].stdout.read(n, function(msgv) {
							check = true;
							assert(msgv.length == n);
							var result = reading(i, msgv);
							logger.info('RETURN ' + subordinate[i].name + '['
									+ i + ']');
							if (result.length > 0) {
								self.stdin.send(result);
							}
							backward(++i, size + result.length, back);
						});
						assert(check);
						check = false;
					} else {
						logger.info('RETURN ' + subordinate[i].name + '[' + i
								+ ']');
						backward(++i, size, back);
					}
				} else {
					logger.info('SKIP: ' + subordinate[i].name + '[' + i
							+ ']: remains stable');
					backward(++i, size, back);
				}
			} else {
				if (size > 0) {
					self.stdin.flush();
					forward(size, false);
				} else {
					assert(typeof self.unstable == 'boolean');
					assert(typeof back == 'boolean');
					if (back) {
						if (self.unstable) {
							self.stdout.flush();
						} else {
							self.stdout.flush();
							you.send([ me ]);
							you.flush();
						}
					}
					continuation();
				}
			}
		}

		function writing(msg) {
			logger.info('MSG:');
			logger.info(msg);
			if (typeof msg == 'number') {
				if (msg < 0) {
					msg = -msg;
					var aux = lookup[msg];
					if (typeof subordinate[aux[0]] == 'undefined') {
						logger.info('ON ' + msg + ': SEND ' + aux[1] + ' @ '
								+ self.name + ' = EOF');
						self.stdout.send([ aux[1] ]);
						return true;
					} else {
						logger.info('ON ' + msg + ': SEND ' + aux[1] + ' @ '
								+ subordinate[aux[0]].name + '[' + aux[0]
								+ '] = EOF');
						subordinate[aux[0]].stdin.send([ -aux[1] ]);
						subordinate[aux[0]].unstable = true;
					}
				} else {
					logger.info('MARK ' + subordinate[msg].name + '[' + msg
							+ '] as unstable');
					subordinate[msg].unstable = true;
				}
			} else {
				var fd = msg[0];
				if (fd >= 0) {
					fd = redirect[fd];
					assert(fd > 0);
				} else {
					fd = -fd;
				}
				if (msg.length == 2 && typeof fd == 'number') {
					var aux = lookup[fd];
					if (typeof aux != 'undefined' && aux[0] >= 0) {
						if (typeof subordinate[aux[0]] == 'undefined') {
							msg[0] = aux[1];
							logger.info('ON ' + fd + ': SEND ' + msg[0] + ' @ '
									+ self.name);
							self.stdout.send([ msg ]);
							return true;
						} else {
							msg[0] = aux[1];
							logger.info('ON ' + fd + ': SEND ' + msg[0] + ' @ '
									+ subordinate[aux[0]].name + '[' + aux[0]
									+ ']');
							subordinate[aux[0]].stdin.send([ msg ]);
							subordinate[aux[0]].unstable = true;
						}
					} else {
						logger.error('bad descriptor: ' + fd);
					}
				} else {
					logger.error('Superior[' + name + ']: bad message:');
					logger.error(msg);
				}
			}
			return false;
		}

		function forward(size, back) {
			logger.trace('forward(size,back):');
			logger.trace(size);
			logger.trace(back);
			if (size > 0) {
				var check = false;
				self.stdin.read(1, function(msgv) {
					check = true;
					if (msgv.length == 0) {
						logger.info('terminated on EOF');
					} else {
						assert(msgv.length == 1);
						if (back) {
							writing(msgv[0]);
						} else {
							back = writing(msgv[0]);
						}
						forward(--size, back);
					}
				});
				assert(check);
				check = false;
			} else {
				++counter;
				backward(0, 0, back);
			}
		}

	}

	return result;
})();
